2010/07/15

簡易 3D 照片製作法: 使用 processing


自從 2009 年的 Avatar 掀起 3D 電影風潮以來,現在 3D 影像算是挺流行的話題,各家科技廠商相繼推出相關產品,這在今年 Computex 2010 的 3D 主題館可見冰山一角。

3D 視覺是個老話題了,1990年代末期台灣跟日本也流行了好一陣子的 3D 立體書 (stereogram),這裡也就不多做介紹了。這篇要講的主要是一個簡易自製紅藍偏光 3D 照片的方法。


3D 影像 


目前主流的 3D 影像,用的都是深度知覺(depth perception)的「雙眼線索」(binocular cues),也就是利用人的雙眼在注視物體時一個微小的角度差,來產生立體感。這類影像被通稱為 stereoscopy,有兩張照片並列的,有疊影的(像是目前主流的 3D 電影),有紅藍色差疊影的,也有混合在雜訊裡隱藏起來的(stereogram)。各種 3D 圖像都有簡易的製作工具,以及相關的原理介紹,請自行參考連結。





Processing

Processing 是一個 Java-based 的 scripting 語言,是個自由軟體,可以到此下載。Processing 常常用來做資料視覺化(Data Visualization)跟互動技術。交大的「互動科技」也有用到這個軟體,所以現在也有中文的教學可以參考。

如果只是要做 3D 圖,倒不必對這個語言有太深的涉獵,只要會安裝並且執行這個軟體即可。




實作程序
 
這是參考 O'Reilly Answers 上的一篇 DIY 3D p hotography with Processing
  1. 對同一個物體連續拍兩張照片,第一張用左眼瞄準,第二張用右眼,基本上兩張的差別只在相機轉一個小小的角度。建議像片的解析度不要用太高,不然 processing 會 out of memory(如果自己會改 Java VM 的記憶體用量的話,則不受此限)。
  2. 把兩張照片存到電腦裡,因為 processing 認的是絕對路徑,假設兩張照片分別存放在:"/Users/linbay/work/photo_left.jpg" 跟 "/Users/linbay/work/photo_right.jpg"
  3. 打開 processing,執行以下的 script,記得要把前三行改成相對應的位置(第一行是左眼照片,第二行是右眼照片,第三行是輸出圖檔),程式會自動產生新圖檔,按下 "e" 件則會儲存,按 "esc" 跳出。

PImage left = loadImage("/Users/odewahn/Desktop/lake_l.png");
PImage right = loadImage("/Users/odewahn/Desktop/lake_r.png");
String outfile = "/Users/odewahn/Desktop/out.png";
int rx = 0;  // x offest
int ry = 0;  // y offset
int dSize = 10;  // # of pixels to shift
Intersection overlap;
PImage merged;
class Intersection {
  //Defines the overlap reqion
  int w, h; // width and height of overlap 1
  int x, y; // x,y of the top left point of the overlap
  
  // Defines the width and height of the 2 reqions.  They must be of equal size.
  int W, H;  // Width and height of the 2 regions
  int rx, ry;  // x,y position of region
  
  //Defines the mapping of a point p in the overlap into a point in region 1 and region 2
  int eqX1, eqY1;
  int eqX2, eqY2;
  
  //Construct a new region
  Intersection(int _W, int _H) {
    W = _W;
    H = _H;
  }
  
  // Set the offset values of region 2
  void setOffset (int _rx, int _ry) {
    rx = _rx;
    ry = _ry;
    compute();
  }
  
  //Computes the intersection between the two image based on the current offset
  void compute() {
    if (rx < 0) {
       if (ry < 0) {
          // Case A
          w = W + rx;
          h = H + ry;
          x = 0;
          y = 0;
       } else {
          //Case C
          w = W + rx;
          h = H - ry;
          x = 0;
          y = ry;
       }
    } else {
       if (ry < 0) {
          //Case B
          w = W - rx;
          h = H + ry;
          x = rx;
          y = 0;
       } else {
          //Case D
          w = W - rx;
          h = H - ry;
          x = rx;
          y = ry;
       }
    }
  }
  
  //Given a point (x,y) in the intersection, this method finds the correspongin point in the 2 regions
  void findEquivPoint (int x, int y) {
    if (rx < 0) {
       if (ry < 0) {
          // Case A
          eqX1 = x;
          eqY1 = y;
          eqX2 = x - rx;
          eqY2 = y - ry;
       } else {
          //Case C
          eqX1 = x;
          eqY1 = y + ry;
          eqX2 = x - rx;
          eqY2 = y;
       }
    } else {
       if (ry < 0) {
          //Case B
          eqX1 = x + rx;
          eqY1 = y;
          eqX2 = x;
          eqY2 = y - ry;
       } else {
          //Case D
          eqX1 = x + rx;
          eqY1 = y + ry;
          eqX2 = x;
          eqY2 = y;
       }
    }     
  }
}
//Create a merged image
PImage merge() {
  int loc;
  PImage merged = createImage(overlap.w, overlap.h, RGB);
  left.loadPixels();
  right.loadPixels();
  merged.loadPixels();
  //now process the left and right pixels one by one and merge them into a new color
  for (int x = 0; x < overlap.w; x++) {
    for (int y = 0; y < overlap.h; y++) {
      overlap.findEquivPoint(x,y);
      loc = overlap.eqX1 + overlap.eqY1*overlap.W;
      //Pull out RGB for the left image
      float r1 = red(left.pixels[loc]);
      float g1 = green(left.pixels[loc]);
      float b1 = blue(left.pixels[loc]);
      // Pull rgb for the right image
      loc = overlap.eqX2 + overlap.eqY2*overlap.W;
      float r2 = red(right.pixels[loc]);
      float g2 = green(right.pixels[loc]);
      float b2 = blue(right.pixels[loc]);
      //Compute new pixel color in the merged image
      float r = 0.299 * r1 + 0.587 * g1 + 0.114 * b1;
      float g = g2;
      float b = b2;
      //Now set new color
      loc = x + y*merged.width;
      merged.pixels[loc] = color(r,g,b);
    }
  }
  return merged;
}


void setup() {
  size(left.width, left.height);
  overlap = new Intersection(left.width, left.height);
  overlap.setOffset(0,0);
  merged = merge();
}

void keyPressed() {
  switch (key) {
     case 'q':
        ry -= dSize;
        break;
     case 'a':
        ry += dSize;
        break;
     case 'l':
        rx += dSize;
        break;
     case 'k':
        rx -= dSize;
        break;
     case '1':
        dSize = 10;
        break;
     case '2':
        dSize = 1;
        break;
     case 'e':
        merged.save(outfile);
        break;
  }
  overlap.setOffset(rx,ry);
  merged = merge();
}
void draw() {
   background(#ffffff);
   image(merged,0,0);
}  
Have fun!

沒有留言: